#include "Bloque.h"
#include "funciones.h"
#include "Registro.h"
#include "funciones.h"

#include "Excepciones.h"

namespace Toolkit
{
Bloque::Bloque(const unsigned long int &tamanoBloque,
		const TipoSerializacion &tSerializacion) :
	Componente(tSerializacion)
{
	this->tamanoBloque = tamanoBloque;
}

Bloque::~Bloque()
{
	list<Componente*>::iterator it = componentes.begin();
	while (it != componentes.end())
	{
		delete *it;
		it++;
	}
}

void Bloque::serializar(stringstream& s) const
{
	stringstream ssDataBloque;

	for (list<Componente*>::const_iterator it = componentes.begin(); it
			!= componentes.end(); it++)
	{
		stringstream ssAuxComp;
		(*it)->serializar(ssAuxComp);

		ssDataBloque.write(ssAuxComp.str().c_str(), ssAuxComp.str().size());
	}

	string aux = this->especializarSerializacion();
	aux.append(ssDataBloque.str().c_str(), ssDataBloque.str().size());

	ssDataBloque.str(aux);

	agregarTamanoASerializacionComp(ssDataBloque, tSerializacion);

	char tipo = this->getTipo();
	s.write(&tipo, sizeof(char));

	s.write(ssDataBloque.str().c_str(), ssDataBloque.str().size());
}

void Bloque::hidratar(stringstream& s, const Dato &dato)
{
	char tipo = 0;
	int tamano = 0;
	char *pMetaYDatos;

	s.read((char *) &tipo, sizeof(char));

	if (getTipo() == tipo)
	{
		hidratacionDigito(s, tSerializacion, tamano);

		pMetaYDatos = new char[tamano];//No hace falta tener en cuenta el \0. Se esta usando al char[] como un buffer de bytes
		s.read(pMetaYDatos, tamano);

		stringstream ssMetaYData;
		ssMetaYData.write(pMetaYDatos, tamano); //Copio la informacion del stringstream desde el cual hidrato, y solo trabajo con la parte que me interesa

		int posicionOriginal = ssMetaYData.tellg();
		this->especializarHidratacion(ssMetaYData);

		int porProcesar = tamano - ((int) ssMetaYData.tellg()
				- posicionOriginal);
		char tipoLeido = 0;

		while (porProcesar > 0)
		{
			int posComponente = ssMetaYData.tellg();

			ssMetaYData.read(&tipoLeido, sizeof(char));

			Componente *c = NULL;
			if (tipoLeido == 'B')
			{
				c = new Bloque(tamanoBloque, tSerializacion);
			}
			else if (tipoLeido == 'R')
			{
				c = new Registro(tSerializacion);
			}

			int tamanoComponente = 0;
			hidratacionDigito(ssMetaYData, tSerializacion, tamanoComponente);

			int tamanoTotalComponente = tamanoComponente
					+ ((int) ssMetaYData.tellg() - posComponente);

			//Retrocedo el puntero para incluir el tipo y el tamaño del componente
			ssMetaYData.seekg(posComponente, ios_base::beg);

			char* buffer = new char[tamanoTotalComponente];
			ssMetaYData.read(buffer, tamanoTotalComponente);
			porProcesar -= tamanoTotalComponente;

			stringstream serializacionComponente;
			serializacionComponente.write(buffer, tamanoTotalComponente);

			c->hidratar(serializacionComponente, dato);

			delete[] buffer;

			componentes.push_back(c);
		}
		delete[] pMetaYDatos;
	}
}

long int Bloque::espacioLibre() const
{
	return tamanoBloque - getTamanoSerializacion();
}

unsigned long int Bloque::getTamanoBloque() const
{
	return tamanoBloque;
}

void Bloque::agregarSinChequearMaximo(Componente *componente,
		const Clave& clave)
{

	list<Componente*>::iterator it = componentes.begin();

	if (componentes.size() > 0)
	{
		while (it != componentes.end())
		{
			if ((**it) == clave)
			{
				throw ClaveDuplicadaException();
			}
			it++;
		}
	}
	componentes.insert(it, componente);
}

void Bloque::agregar(Componente *componente, const Clave & clave)
{
	stringstream s;
	componente->serializar(s);

	int espacioComponente = s.str().size();

	if ((getTamanoSerializacion() + espacioComponente) > tamanoBloque)
	{
		throw DesbordeDeBloqueException();
	}
	else
	{
		agregarSinChequearMaximo(componente, clave);
	}

}

Componente* Bloque::remover(const Clave & clave)
{
	Componente* c = buscar(clave);
	if (c == NULL)
	{
		return NULL;
	}

	componentes.remove(c);
	return c;
}

Componente* Bloque::buscar(const Clave & clave)
{
	list<Componente*>::iterator it = componentes.begin();
	while (it != componentes.end())
	{
		if (**it == clave)
		{
			return *it;
		}
		it++;
	}

	return NULL;
}

Componente* Bloque::popBack()
{
	if (!componentes.empty())
	{
		Componente *aux = componentes.back();
		componentes.pop_back();
		return aux;
	}
	else
	{
		return NULL;
	}
}

Componente* Bloque::popFront()
{
	if (!componentes.empty())
	{
		Componente *aux = componentes.front();
		componentes.pop_front();
		return aux;
	}
	else
	{
		return NULL;
	}
}

bool Bloque::operator <(const Clave &clave) const
{
	return (*componentes.back()) < clave;
}

bool Bloque::operator ==(const Clave &clave) const
{
	return (*componentes.back()) == clave;
}

int Bloque::cantElementos() const
{
	return componentes.size();
}

char Bloque::getTipo() const
{
	return 'B';
}

string Bloque::especializarSerializacion() const
{
	return "";
}

void Bloque::especializarHidratacion(stringstream&)
{

}

Componente *Bloque::clonar() const
{
	Bloque *nuevo = new Bloque(tamanoBloque, tSerializacion);

	for (list<Componente*>::const_iterator it = componentes.begin(); it
			!= componentes.end(); it++)
	{
		nuevo->componentes.push_back((*it)->clonar());
	}

	return nuevo;

}

list<Clave*> Bloque::getClaves(const Clave &claveMolde) const
{
	list<Clave*> listaClave;

	if (this->cantElementos() != 0)
	{
		for (list<Componente*>::const_iterator it = this->componentes.begin(); it
				!= this->componentes.end(); it++)
		{
			list<Clave*> listaClaveAux = (*it)->getClaves(claveMolde);

			listaClave.insert(listaClave.end(), listaClaveAux.begin(),
					listaClaveAux.end());
		}
	}

	return listaClave;
}

bool Bloque::puedeAgregarse(Componente *componente)
{
	componentes.push_back(componente);
	bool resultado = (espacioLibre() >= 0);
	componentes.pop_back();

	return resultado;
}

}

